package com.ccteam.fluidmusic.ui

import android.app.Application
import android.net.Uri
import android.os.Bundle
import android.os.ResultReceiver
import android.support.v4.media.MediaDescriptionCompat
import android.support.v4.media.MediaMetadataCompat
import android.support.v4.media.session.PlaybackStateCompat
import androidx.core.os.bundleOf
import androidx.lifecycle.*
import com.ccteam.fluidmusic.R
import com.ccteam.fluidmusic.fluidmusic.common.MusicServiceConnectionFlow
import com.ccteam.fluidmusic.fluidmusic.common.utils.BitrateTrackCommandReceiver.Companion.COMMAND_SELECT_BITRATE
import com.ccteam.fluidmusic.fluidmusic.common.utils.BitrateTrackCommandReceiver.Companion.COMMAND_UPDATE_BITRATE
import com.ccteam.fluidmusic.fluidmusic.common.utils.BitrateTrackCommandReceiver.Companion.COMMAND_UPDATE_RENDER_INDEX
import com.ccteam.fluidmusic.fluidmusic.common.utils.BitrateTrackData
import com.ccteam.fluidmusic.fluidmusic.common.utils.MediaIDHelper
import com.ccteam.fluidmusic.fluidmusic.common.utils.isLocalMediaSdk29
import com.ccteam.fluidmusic.fluidmusic.core.SettingCore
import com.ccteam.fluidmusic.fluidmusic.media.extensions.*
import com.ccteam.fluidmusic.utils.cancelIfActive
import com.ccteam.fluidmusic.view.bean.NowPlayingMediaData
import com.ccteam.fluidmusic.view.bean.PlaylistMediaData
import com.ccteam.fluidmusic.view.bean.SearchMoreInfoData
import com.ccteam.shared.domain.music.AddHotMusicCountDataUseCase
import com.ccteam.shared.utils.EMPTY_STRING
import com.orhanobut.logger.Logger
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import javax.inject.Inject

/**
 *
 * @author Xiaoc
 * @since 2020/12/29
 *
 * Activity的ViewModel
 * 其中定义了一些基本方法，包括播放歌曲，切换歌曲等操作
 */
@HiltViewModel
class MainActivityViewModel @Inject constructor(
    private val settingCore: SettingCore,
    application: Application,
    private val addHotMusicCountDataUseCase: AddHotMusicCountDataUseCase,
    private val musicServiceConnectionFlow: MusicServiceConnectionFlow
): AndroidViewModel(application) {

    /**
     * 专辑封面Uri
     * 由于 AndroidQ 以上不能直接通过专辑Uri去获取封面
     * 所以使用Pair，first为存储的Uri（用来判断需不需要进行专辑封面更新），second才是真正解析专辑封面的Uri
     */
    private val _mediaAlbumArt = MutableLiveData<Pair<Uri?,Uri?>>()
    val mediaAlbumArt: LiveData<Pair<Uri?,Uri?>> get() = _mediaAlbumArt

    /**
     * 重复模式，是一个Triple类型
     * 第一个Int为具体模式的值
     * 第二个为该模式的描述
     * 第三个为该模式对呀的Icon值
     */
    val repeatMode: LiveData<Pair<Int,Int>> = musicServiceConnectionFlow.nowRepeatMode.map { repeatMode ->
        when (repeatMode) {
            PlaybackStateCompat.REPEAT_MODE_ALL ->{
                Pair(
                    repeatMode,
                    R.drawable.ic_repeat
                )
            }
            PlaybackStateCompat.REPEAT_MODE_ONE ->{
                Pair(
                    repeatMode,
                    R.drawable.ic_repeat_one
                )
            }
            else ->{
                Pair(
                    repeatMode,
                    R.drawable.ic_repeat_off
                )
            }
        }
    }.asLiveData()

    val shuffleMode: LiveData<Boolean> = musicServiceConnectionFlow.nowShuffleMode.map {
        when(it){
            PlaybackStateCompat.SHUFFLE_MODE_ALL,
            PlaybackStateCompat.SHUFFLE_MODE_GROUP ->{
                true
            }
            else ->{
                false
            }
        }
    }.asLiveData()

    /**
     * 当前播放列表数据
     * 使用 [MediatorLiveData] 是因为有多个因素去影响（取色颜色和当前播放项）
     * 所以需要添加多数据源进行观察
     */
    private val _playlistMediaData = MediatorLiveData<List<PlaylistMediaData>>()
    val playlistMediaData: LiveData<List<PlaylistMediaData>> get() = _playlistMediaData

    /**
     * 记录当前播放处于播放列表的哪一个位置，方便定位
     */
    var currentItemInPlaylistIndex = 0

    val mediaButtonRes = MutableLiveData(R.drawable.ic_play_filled)

    val localRootId: LiveData<String?> = musicServiceConnectionFlow.isConnected.map { isConnected ->
        if(isConnected){
            musicServiceConnectionFlow.localRootId
        } else {
            null
        }
    }.asLiveData()

    val nowPlayingMediaData: LiveData<NowPlayingMediaData> = musicServiceConnectionFlow.nowPlaying.map { metadata ->
        updateMediaArt(metadata)
        NowPlayingMediaData(
                metadata.id ?: EMPTY_STRING,
                metadata.title,
                metadata.artist,
                metadata.album,
                metadata.duration,
                metadata.isOffline == 1L,
            metadata.mediaUri ?: Uri.EMPTY
        )
    }.flowOn(Dispatchers.Default).asLiveData(Dispatchers.Default)

    private var mediaButtonJob: Job? = null
    private var playlistJob: Job? = null

    init {

        _playlistMediaData.value = emptyList()
        mediaButtonJob = viewModelScope.launch {
            musicServiceConnectionFlow.nowPlaybackState.collectLatest {
                mediaButtonRes.value = updatePlaybackState(it)
            }
        }
        playlistJob = viewModelScope.launch(Dispatchers.Default) {
            musicServiceConnectionFlow.nowPlaylistQueue.collectLatest { playlist ->
                _playlistMediaData.postValue(handlePlaylistQueue(
                    musicServiceConnectionFlow.nowPlaying.value,
                    playlist
                ))
            }
        }

        /**
         * 监听定时关闭任务是否发生变化
         */
        viewModelScope.launch {
            settingCore.musicTimer.collectLatest {
                musicServiceConnectionFlow.timerClose(it.enableTimer,it.timerCount)
            }
        }
    }

    /**
     * 通过mediaId播放歌曲
     * 该方法还需传入parentId用于辨识当前处于哪个父类分项，以及是否为在线或本地音乐类型
     * 该方法会判断当前播放与准备播放的ID是否一致，如果一致则进行暂停或回复播放操作
     *
     * @param isRefresh 是否重新开始播放
     */
    fun playMediaId(mediaId: String,
                    parentId: String?,
                    isOffline: Boolean,
                    isRefresh: Boolean = false){

        val nowPlaying = musicServiceConnectionFlow.nowPlaying.value
        val controller = musicServiceConnectionFlow.getMediaController() ?: return

        val isPrepared = musicServiceConnectionFlow.nowPlaybackState.value.isPrepared

        // 如果缓冲准备完成，但是为当前播放项，则执行暂停或恢复播放操作
        if(isRefresh && isPrepared && mediaId == nowPlaying.id){
            musicServiceConnectionFlow.nowPlaybackState.value.let { state ->
                when{
                    state.isPlaying ->
                        controller.transportControls?.pause()
                    state.isPlayEnabled ->
                        controller.transportControls?.play()
                    else -> {
                        Logger.w("播放项被点击但是没有开启播放功能 $mediaId")
                    }
                }
            }
        } else {
            val bundle = Bundle().apply {
                putString(MediaIDHelper.PARENT_ID_TAG,parentId)
                putLong(MediaIDHelper.IS_OFFLINE_TAG, if(isOffline) 1L else 0L)
            }
            controller.transportControls?.playFromMediaId(mediaId,bundle)
        }
        viewModelScope.launch {
            addHotMusicCountDataUseCase(parentId to mediaId)
        }
    }

    /**
     * 暂停或播放音乐
     */
    fun playOrPause(){
        val controller = musicServiceConnectionFlow.getMediaController()?.transportControls ?: return
        val playbackState = musicServiceConnectionFlow.nowPlaybackState.value

        if(playbackState.isPlaying){
            controller.pause()
        } else {
            controller.play()
        }
    }

    /**
     * 跳转到下一首
     */
    fun skipNext(){
        val controller = musicServiceConnectionFlow.getMediaController()?.transportControls ?: return
        controller.skipToNext()
    }

    /**
     * 跳转到上一首
     */
    fun skipPrevious(){
        val controller = musicServiceConnectionFlow.getMediaController()?.transportControls ?: return
        controller.skipToPrevious()
    }

    /**
     * 跳到播放列表的某一项，并自动播放
     */
    fun skipQueueItem(queueId: Long){
        val controller = musicServiceConnectionFlow.getMediaController()?.transportControls ?: return
        controller.skipToQueueItem(queueId)
    }

    /**
     * 设置随机播放
     */
    fun setShuffleMode(@PlaybackStateCompat.ShuffleMode shuffleMode: Int){
        val controller = musicServiceConnectionFlow.getMediaController()?.transportControls ?: return
        controller.setShuffleMode(shuffleMode)
    }

    /**
     * 设置播放顺序模式
     */
    fun setRepeatMode(@PlaybackStateCompat.RepeatMode repeatMode: Int){
        val controller = musicServiceConnectionFlow.getMediaController()?.transportControls ?: return
        controller.setRepeatMode(repeatMode)
    }

    /**
     * 移除播放列表中某一项内容
     */
    fun removeQueueItem(queueId: Long){
        val controller = musicServiceConnectionFlow.getMediaController() ?: return
        val extra = bundleOf(METADATA_KEY_QUEUE_ID to queueId)
        controller.removeQueueItem(
            MediaDescriptionCompat.Builder().setExtras(extra).build()
        )
    }

    /**
     * 添加内容到播放列表中
     * 该方法指定对应媒体的ID和它的父ID
     * 会自动根据这两项指标进行查找然后添加到播放列表中
     *
     * @param addPlaylistType 加入到播放列表的类型
     */
    fun addQueueItem(mediaId: String,parentId: String,addPlaylistType: Int){
        val controller = musicServiceConnectionFlow.getMediaController() ?: return
        val extra = bundleOf(MediaIDHelper.PARENT_ID_TAG to parentId)

        when(addPlaylistType){
            MediaIDHelper.PLAYLIST_ADD_TYPE_NOW_PLAY ->{
                extra.putInt(MediaIDHelper.PLAYLIST_ADD_TYPE,MediaIDHelper.PLAYLIST_ADD_TYPE_NOW_PLAY)
            }
            MediaIDHelper.PLAYLIST_ADD_TYPE_NEXT_PLAY ->{
                extra.putInt(MediaIDHelper.PLAYLIST_ADD_TYPE ,MediaIDHelper.PLAYLIST_ADD_TYPE_NEXT_PLAY)
            }
            MediaIDHelper.PLAYLIST_ADD_TYPE_LAST_PLAY ->{
                extra.putInt(MediaIDHelper.PLAYLIST_ADD_TYPE ,MediaIDHelper.PLAYLIST_ADD_TYPE_LAST_PLAY)
            }
        }

        controller.addQueueItem(
            MediaDescriptionCompat.Builder()
                .setMediaId(mediaId)
                .setExtras(extra).build()
        )

    }

    /**
     * 添加内容到播放列表中
     * 类似于搜索之类的内容，需要将音乐信息添加到播放列表中
     *
     * @param music 音乐信息
     * @param addPlaylistType 添加播放列表的类型（详见MediaIdHelper）
     */
    fun addQueueItem(music: SearchMoreInfoData, addPlaylistType: Int){
        val controller = musicServiceConnectionFlow.getMediaController() ?: return
        if(music.mediaUrl.isNullOrEmpty()){
            return
        }
        val metadata = MediaMetadataCompat.Builder().apply {
            id = music.id
            duration = music.duration
            title = music.mediaTitle
            album = music.albumName
            albumId = music.albumId
            displayTitle = music.mediaTitle
            displaySubtitle = music.albumName
            displayDescription = music.artistInfo
            artist = music.artistInfo
            trackNumber = music.track.toLong()
            mediaUri = music.mediaUrl
            albumArtUri = music.imgUrl
            displayIconUri = music.imgUrl
        }.build()
        val extra = bundleOf(MediaIDHelper.CACHE_PLAY to true,
            MediaIDHelper.CACHE_PLAY_METADATA to metadata)

        extra.putInt(MediaIDHelper.PLAYLIST_ADD_TYPE,addPlaylistType)

        controller.addQueueItem(
            MediaDescriptionCompat.Builder()
                .setExtras(extra).build()
        )
    }

    /**
     * 跳转到对应播放位置
     * @param autoPlay 是否跳转后自动播放
     */
    fun seekTo(position: Long, autoPlay: Boolean = false){
        val controller = musicServiceConnectionFlow.getMediaController()?.transportControls ?: return
        controller.seekTo(position)
        if(autoPlay){
            controller.play()
        }
    }

    /**
     * 得到当前音源的Bitrate列表
     * @param resultReceiver 通过该对象进行进程间通信回调
     */
    fun commandGetBitrate(resultReceiver: ResultReceiver?){
        val controller = musicServiceConnectionFlow.getMediaController() ?: return
        controller.sendCommand(
            COMMAND_SELECT_BITRATE, null,
            resultReceiver
        )
    }

    /**
     * 设置当前音源的Bitrate音质
     * @param resultReceiver 通过该对象进行进程间通信回调
     */
    fun setCurrentBitrate(resultReceiver: ResultReceiver?,trackData: BitrateTrackData?,renderIndex: Int){
        val controller = musicServiceConnectionFlow.getMediaController() ?: return
        controller.sendCommand(
            COMMAND_UPDATE_BITRATE, bundleOf(COMMAND_UPDATE_BITRATE to trackData,
                COMMAND_UPDATE_RENDER_INDEX to renderIndex),
            resultReceiver
        )
    }

    /**
     * 当播放内容发生变化后，更新UI内容
     * 例如播放/暂停按钮
     */
    private fun updatePlaybackState(playbackState: PlaybackStateCompat): Int{
        return when(playbackState.isPlaying) {
            true -> R.drawable.ic_pause_filled
            false -> R.drawable.ic_play_filled
        }
    }

    /**
     * 更新MediaMetadata信息
     * 当播放内容更新时，判断是否需要去更新专辑封面地址
     * 如果专辑封面地址与之前是一致的，则不需要去更新封面地址
     *
     * 在Flow的Default线程中进行更新
     */
    private fun updateMediaArt(metadata: MediaMetadataCompat){
        // 专辑封面的存储Uri
        val cacheKeyUri = metadata.albumArtUri
        // 真正需要去解析的封面地址（由于AndroidQ以上专辑封面解析Uri和实际Uri不同）
        var realParseUri = metadata.albumArtUri
        // 如果是本地资源且版本号大于Android Q才进行这些操作
        if(realParseUri.isLocalMediaSdk29()){

            // 如果是AndroidQ的本地资源，封面Uri为Music的Uri
            realParseUri = metadata.mediaUri
        }

        if(cacheKeyUri != _mediaAlbumArt.value?.first || _mediaAlbumArt.value?.first == null){
            _mediaAlbumArt.postValue(Pair(cacheKeyUri,realParseUri))
        }
    }

    /**
     * 处理播放列表
     * 收到播放列表更改的通知后
     * 将 [MediaMetadataCompat] 转换为 [PlaylistMediaData]
     * 其中 queueId 就是对应List中的下标索引
     * 如果当前音乐正在播放，还会将 isPlaying 值置为 true
     */
    private fun handlePlaylistQueue(nowPlayingMetadata: MediaMetadataCompat,
                                    playlistQueue: List<MediaMetadataCompat>): List<PlaylistMediaData>{
        return playlistQueue.mapIndexed { index, playlistItem ->
            val isPlaying = checkPlaylistItemIsPlaying(nowPlayingMetadata,index.toLong(),playlistItem.id!!)
            if(isPlaying) currentItemInPlaylistIndex = index
            PlaylistMediaData(
                playlistItem.id!!,
                isPlaying,
                index.toLong(),
                playlistItem.title,
                playlistItem.artist,
                playlistItem.albumArtUri
            )
        }
    }

    private fun checkPlaylistItemIsPlaying(nowPlayingMetadata: MediaMetadataCompat,
                                           queueId: Long, mediaId: String): Boolean{
        return nowPlayingMetadata.id == mediaId && nowPlayingMetadata.queueId == queueId
    }

    override fun onCleared() {
        super.onCleared()

        mediaButtonJob.cancelIfActive()
        playlistJob.cancelIfActive()
    }
}